/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Force-TLS.
 *
 * The Initial Developer of the Original Code is
 * Sid Stamm.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Sid Stamm <sid@mozilla.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * Change Log:
 * v4.0   (2/21/2014):
 *   <sid>: removed logic for custom back-end.  (Issue 22).
 * v3.0.2 (3/27/2012):
 *   <sid>: updated calls to nsIStrictTransportSecurityService to pass in flags
 *         after the arguments.
 *   <sid>: Fixed issue where permissions set to DENY_ACTION were mistakenly
 *         shown in the UI as forced sites.
 * v3.0   (3/10/2011):
 *   <sid>: Changed to be a proxy that chooses either the built-in HSTS
 *         support in Firefox 4.0 and similar or our own back-end when that's
 *         not present.
 * v2.0   (9/24/2009):
 *   <sid>: changed upgrading mechanism to upgrade URIs as they hit the wire
 *         instead of as they are created.  This way http URIs can still be
 *         used, but are never requested.  This is done via nsIContentPolicy --
 *         creating a policy that modifies the URIs.  (Maybe not ideal, but the
 *         only way to keep the UI happy.)
 *   <sid>: Added support for the Strict-Transport-Security header alias (same
 *         as X-Force-TLS).
 *   <sid>: Added support for multiple header values that are joined with
 *         commas.
 */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

// -- this has to be imported later, it's not available when this module is
//     created.
//Components.utils.import("resource://forcetls/ForceTLSUtils.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;

// SWITCH for HSTS/SSS usage.  Refactoring STSS->SSS happened in Firefox 26.
const HAS_HSTS = ('nsIStrictTransportSecurityService' in Components.interfaces);
const HAS_SSS  = ('nsISiteSecurityService' in Components.interfaces);

function ForceTLSRecord() { }
ForceTLSRecord.prototype = {
  classDescription: "ForceTLS UI Record class",
  classID:          Components.ID("{825BDD47-5F93-4488-B84E-711A16D3DFCF}"),
  contractID:       "@sidstamm.com/ftlsrecord;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.IForceTLSRecord]),

  host: 'undefined',
  includeSubdomains: false,
  expireTime: 0,
  expireType: 0
};


/***********************************************************
  component definition
***********************************************************/
function ForceTLS() {
  Components.utils.import("resource://forcetls/ForceTLSUtils.jsm");
  FTLSDebug("XPCOM ForceTLS service creation.");
}

// class definition
ForceTLS.prototype = {

  // properties required for XPCOM registration:
  classDescription: "ForceTLS Extension XPCOM Component",
  classID:          Components.ID("{FEED8351-AF08-4CA5-9354-4316D843E182}"),
  contractID:       "@sidstamm.com/forcetls;1",
  _xpcom_categories: [{ category: "app-startup", service: true },
                      { category: "content-policy"}],
  QueryInterface: XPCOMUtils.generateQI([Ci.IForceTLS,
                                         Ci.nsIContentPolicy,
                                         Ci.nsISupportsWeakReference,
                                         Ci.nsIObserver]),


  /**
   * exposed interface that can be queried (from our protocol handler) to
   * determine whether or not to enforce ForceTLS.
   */
shouldForceTLS:
  function(host) {
    FTLSDebug("Checking STS state for " + host);
    if (HAS_HSTS) return this.stss.isStsHost(host, 0);
    if (HAS_SSS) return this.sss.isSecureHost(host, 0);
    FTLSDebug("ERROR: no HSTS backend installed.");
    return false;
  },

get stss() {
    if (!this._stss) {
      this._stss = Cc["@mozilla.org/stsservice;1"]
                     .getService(Ci.nsIStrictTransportSecurityService);
    }
    return this._stss;
  },

get sss() {
    if (!this._sss) {
      this._sss = Cc["@mozilla.org/ssservice;1"]
                     .getService(Ci.nsISiteSecurityService);
    }
    return this._sss;
  },

processStsHeader:
  function(aSourceURI, aHeader) {
    FTLSDebug("Processing STS header: " + aHeader);
    if (HAS_HSTS) return this.stss.processStsHeader(aSourceURI, aHeader, 0);
    if (HAS_SSS)  return this.sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
                                                aSourceURI, aHeader, 0);
    FTLSDebug("ERROR: no HSTS backend installed.");
    throw "no HSTS backend";
  },

removeStsState:
  function(aURI) {
    FTLSDebug("Removing STS state for " + aURI.asciiSpec);
    if (HAS_HSTS) return res = this.stss.removeStsState(aURI, 0);
    if (HAS_SSS) return res = this.sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS,
                                                   aURI, 0);
    FTLSDebug("ERROR: no HSTS backend installed.");
    throw "no HSTS backend";
  },

shouldIgnoreStsHeader:
  function(aSecurityInfo) {
    FTLSDebug("Checking Should Ignore STS state for " + aSecurityInfo);
    if (HAS_HSTS) return this.stss.shouldIgnoreStsHeader(aSecurityInfo);
    if (HAS_SSS) return this.sss.shouldIgnoreHeaders(aSecurityInfo);
    FTLSDebug("ERROR: no HSTS backend installed.");
    return false;
  },

isStsHost:
  function(aHost) {
    if (HAS_HSTS) return this.stss.isStsHost(aHost, 0);
    if (HAS_SSS) return this.sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                              aHost, 0);
    FTLSDebug("ERROR: no HSTS backend installed.");
    return false;
  },

isStsURI:
  function(aURI) {
    if (HAS_HSTS) return this.stss.isStsURI(aURI, 0);
    if (HAS_SSS) return this.sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                                             aURI, 0);
    FTLSDebug("ERROR: no HSTS backend installed.");
    return false;
  },

uriHasSubdomainsForced:
  function(aURI) {
    // NOTE: this has its fingers directly in the permission manager

    if (!HAS_HSTS && !HAS_SSS) {
      FTLSDebug("ERROR: no HSTS backend installed.");
      return false;
    }

    var permmgr = Cc['@mozilla.org/permissionmanager;1']
                    .getService(Ci.nsIPermissionManager);
    return (Ci.nsIPermissionManager.ALLOW_ACTION ==
            permmgr.testPermission(aURI, "sts/subd"));
  },

setStsState:
  function(aSourceURI, includeSubdomains, maxage, expireType) {
    // NOTE: this has its fingers directly in the permission manager
    if (!HAS_HSTS && !HAS_SSS) {
      FTLSDebug("ERROR: no HSTS backend installed, won't store permissions.");
      throw "no HSTS backend, not storing the record in the permission manager";
    }
    var now = new Date();
    var permmgr = Cc['@mozilla.org/permissionmanager;1']
                    .getService(Ci.nsIPermissionManager);
    permmgr.add(aSourceURI, "sts/use",
                Ci.nsIPermissionManager.ALLOW_ACTION,
                expireType,
                now.getTime() + (maxage * 1000));

    if (includeSubdomains)
      permmgr.add(aSourceURI, "sts/subd",
                  Ci.nsIPermissionManager.ALLOW_ACTION,
                  expireType,
                  now.getTime() + (maxage * 1000));
    else
      permmgr.remove(aSourceURI.asciiHost, "sts/subd");
  },

get enumerator() {
    return builtinEnumerator(this);
  },

};


/**
 * Data enumerator for applications with a HTTP Strict Transport Security
 * service built-in.
 */
function builtinEnumerator(dataService) {
  var stuff = [];
  var subdomhosts = [];
  var stshosts = [];

  var permmgr = Cc['@mozilla.org/permissionmanager;1']
                  .getService(Ci.nsIPermissionManager);
  var enumerator = permmgr.enumerator;
  while (enumerator.hasMoreElements()) {
    var nextPerm = enumerator.getNext().QueryInterface(Ci.nsIPermission);

    if (nextPerm.type === "sts/subd"
        && nextPerm.capability == Ci.nsIPermissionManager.ALLOW_ACTION)
      subdomhosts.push(nextPerm);
    else if (nextPerm.type === "sts/use"
             && nextPerm.capability == Ci.nsIPermissionManager.ALLOW_ACTION)
      stshosts.push(nextPerm);
  }

  //zip together into one list
  for (var h in stshosts) {
    var subdomains = false;
    for (var s in subdomhosts) {
      if (stshosts[h].host === subdomhosts[s].host) {
        subdomains = true;
        break;
      }
    }
    var rec = Cc['@sidstamm.com/ftlsrecord;1']
                .createInstance(Ci.IForceTLSRecord);
    rec.host = stshosts[h].host;
    rec.includeSubdomains = subdomains;
    rec.expireType = stshosts[h].expireType;
    rec.expireTime = stshosts[h].expireTime;
    stuff.push(rec);
  }

  return {
    i_: 0,
    QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
    hasMoreElements: function bie_hasMoreElements() {
      return this.i_ < stuff.length;
    },
    getNext: function bie_getNext() {
           return stuff[this.i_++];
    }
  };
}

// Do it both ways for 1.9.2- support as well as gecko 2 support.
if (XPCOMUtils.generateNSGetFactory)
  var NSGetFactory = XPCOMUtils.generateNSGetFactory([ForceTLS, ForceTLSRecord]);
else
  var NSGetModule = XPCOMUtils.generateNSGetModule([ForceTLS, ForceTLSRecord]);
